﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.SqlCommand;
using NHibernate.Type;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IProdutoRepository : IRepository<Produto, ProdutoStatistics>
    {
        IEnumerable<Produto> GetModels(int? numero, IEnumerable<Guid> vendedorIds, string cliente, IEnumerable<Guid> fabricaIds, IEnumerable<Guid> transportadoraIds, DateTime? de, DateTime? ate, string codigo, string descricao, out ProdutoStatistics statistics, int page);
    }

    public class ProdutoRepository : RepositoryBase<Produto, ProdutoStatistics, ProdutoError>, IProdutoRepository
    {
        public ProdutoRepository(IRepositoryConfiguration repositoryConfiguration, ISession session)
            : base(repositoryConfiguration, session)
        {
        }

        private IEnumerable<Produto> GetModels(out ProdutoStatistics statistics, int page, Func<ICriteria, string, ICriteria> addPedidoFilters, Func<ICriteria, ICriteria> addProdutoFilters)
        {
            const string PedidoTableAlias = "pe";
            ICriteria pedidosCriteria = Session.CreateCriteria<Pedido>(PedidoTableAlias)
                .Add(Restrictions.Eq(Projections.Property<Pedido>(x => x.Representante.Id), GetRepresentanteId()))
                .AddUserFiltersForPedido(User);
            pedidosCriteria = addPedidoFilters(pedidosCriteria, PedidoTableAlias);
            ICriteria produtosCriteria = Session.CreateCriteria<Produto>()
                .CreateAlias("Pedido", PedidoTableAlias, JoinType.InnerJoin)
                .Add(Subqueries.PropertyIn("Pedido", pedidosCriteria.ToDetachedCriteriaProjectId()))
                .AddOrder(new Order("Codigo", true));
            produtosCriteria = addProdutoFilters(produtosCriteria);
            int quantidadeTotal = Convert.ToInt32(((ICriteria)produtosCriteria.Clone())
                .SetProjection(Projections.Sum<Produto>(x => x.Quantidade))
                .UniqueResult());
            produtosCriteria.SetProjection(Projections.GroupProperty("Codigo"),
                                           Projections.GroupProperty("Descricao"),
                                           Projections.Sum("Quantidade"));
            int modelCount;
            IEnumerable<Produto> produtos = ListPaging(produtosCriteria, page, out modelCount)
                .Select(x => new Produto
                {
                    Codigo = (string) x[0],
                    Descricao = ((string) x[1]).Trim(),
                    Quantidade = (int) x[2],
                });
            statistics = new ProdutoStatistics(modelCount, quantidadeTotal);
            return produtos;
        }

        public override IEnumerable<Produto> GetModelsByIds(IEnumerable<Guid> ids)
        {
            throw new NotImplementedException();
        }

        public override void AddModel(Produto model)
        {
            throw new NotImplementedException();
        }

        public override Produto GetModelById(Guid id)
        {
            throw new NotImplementedException();
        }

        public override void UpdateModel(Produto model)
        {
            throw new NotImplementedException();
        }

        public override void RemoveModelById(Guid id)
        {
            throw new NotImplementedException();
        }

        public override IEnumerable<Produto> GetModels(out ProdutoStatistics statistics, int page = -1)
        {
            return GetModels(out statistics, page, (c, s) => c, c => c);
        }

        public IEnumerable<Produto> GetModels(int? numero, IEnumerable<Guid> vendedorIds, string cliente, IEnumerable<Guid> fabricaIds, IEnumerable<Guid> transportadoraIds, DateTime? de, DateTime? ate, string codigo, string descricao, out ProdutoStatistics statistics, int page)
        {
            Func<ICriteria, string, ICriteria> addPedidoFilters = (pedidoCriteria, tableAlias) => pedidoCriteria
                .AddClienteFilterForPedido(tableAlias, cliente)
                .AddDateFilterForPedido(de, ate)
                .AddFabricaFilterForPedido(fabricaIds)
                .AddNumeroFilterForPedido(numero)
                .AddTransportadoraFilterForPedido(transportadoraIds)
                .AddVendedorFilterForPedido(vendedorIds);
            Func<ICriteria, ICriteria> addProdutoFilters = produtoCriteria =>
            {
                if (!String.IsNullOrWhiteSpace(codigo))
                {
                    produtoCriteria = produtoCriteria.Add(Restrictions.InsensitiveLike(Projections.Property<Produto>(x => x.Codigo), "%" + codigo.ToLower() + "%"));
                }
                if (!String.IsNullOrWhiteSpace(descricao))
                {
                    produtoCriteria = produtoCriteria.Add(Restrictions.InsensitiveLike(Projections.Property<Produto>(x => x.Descricao), "%" + descricao.ToLower() + "%"));
                }
                return produtoCriteria;
            };
            return GetModels(out statistics, page, addPedidoFilters, addProdutoFilters);
        }
    }

    public enum ProdutoError
    {
    }
}
